// See tutorial at https://grpc.io/docs/tutorials/basic/c.html

/*

docker run --rm -p 9002:5000 --name greeter-server greeter-server
docker run --rm -p 5000:5000 --name greeter-server greeter-server
docker run --rm --network=host greeter-cli ./client localhost

 docker build -t greeter-cli -f client/Dockerfile .
 docker build -t greeter-server -f server/Dockerfile .

*/
#include <grpc/grpc.h>
#include <grpcpp/security/server_credentials.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>

#include <algorithm>
#include <chrono>
#include <cmath>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <thread>

#include "ApiTester.h"
#include "common.h"
#include "master.grpc.pb.h"
#define MAX_FILE_SIZE 1024

#include "grpcNameSpace.h"

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerReader;
using grpc::ServerReaderWriter;
using grpc::ServerWriter;
using grpc::Status;

using mdp::MarketDataService;

using mdp::ContractInfoRequest;
using mdp::ContractInfoResponse;
using mdp::MarketDataRequest;
using mdp::MarketDataResponse;
using mdp::TradingStatusRequest;
using mdp::TradingStatusResponse;

using mdp::LastPrice;

using ::google::protobuf::RepeatedField;
// using ::google::protobuf::double;

// extern     vector<stack<SPApiPrice> > g_sharp_market_buffer;

std::string upload_filename;

#include <unistd.h>

#include <iostream>

std::vector<int> res;
#define MARKET

class ServerNode final : public MarketDataService::Service {
 public:
  std::unordered_map<std::string, int> client_id_;

  int client_num_ = 0;
  ApiTester *sharp_api_handle_;

 public:
  int find_client() { return 0; }

  Status GetLiveMarketData(
      ServerContext *context,
      ServerReaderWriter<MarketDataResponse, MarketDataRequest> *stream)
      override {
#ifdef MARKET
    std::cout << "MARKET peer: " << context->peer() << std::endl;

    std::cout << "MARKET peer: " << context->peer() << std::endl;
    std::string curr_peer = context->peer();

    if (client_id_.find(curr_peer) == client_id_.end()) {
      std::cout << "[new peer]" << std::endl;

      client_id_[curr_peer] = client_num_;
      client_num_++;
      std::cout << "[curr peer num]" << client_id_.size() << std::endl;

      sharp_api_handle_->write_handle_[curr_peer] = stream;  // set handle
    }

    int idx = client_id_[curr_peer];

    MarketDataRequest var;
    while (stream->Read(&var)) {
      sharp_api_handle_->lock();
      std::vector<std::string> sub_ticker;
      while (contract_itr != var.contracts().cend()) {
        std::string ticker = contract_itr->contract_id();
        sub_ticker.emplace_back(ticker);
        std::cout << " sub ticker " << ticker << std::endl;
        int rc;
        char prodcode[16], userId[16];
        strncpy(userId, sharp_api_handle_->getUserId().c_str(), 16);

        if (sharp_api_handle_->sub_contract_.find(ticker) !=
            sharp_api_handle_->sub_contract_.end()) {
          auto &client_set = sharp_api_handle_->sub_contract_[ticker];
          client_set.insert(curr_peer);
        } else {
          sharp_api_handle_->sub_contract_[ticker] = std::set<std::string>();
          sharp_api_handle_->sub_contract_[ticker].insert(curr_peer);
        }

        contract_itr++;
      }

      for (auto &ele : sub_ticker) {
        rc = sharp_api_handle_->GetWrapperHandle()->SPAPI_SubscribePrice(
            userId, const_cast<char *>(ele.c_str()),
            1);  // idx, 0 :Uninit Price, 1:Subscribe Price, 2:set time to
                 // return to
        if (rc != 0) printf("\n SubscribeUninitPrice return:%d", rc);
      }

      sharp_api_handle_->unlock();

      while (true) {
        sleep(1);
        // std::cout<<"curr peer in while " <<  curr_peer  << std::endl;
        // int idx = client_id_[curr_peer];
        int idx = 0;

        MarketDataResponse msg;
        LastPrice *last_px_lt = new LastPrice();
        std::cout << "[begin for]" << std::endl;
        for (int i = 0; i < 5; i++) {
          auto *num = last_px_lt->add_last_price();
          num->set_value(i);
        }
        msg.set_allocated_last_price(last_px_lt);

        /*
        for(int i = 0; i < 5; i++){
            auto *num = msg.add_bid();
            num->set_value(i);
         }
         */
        // msg.set_allocated_timestamp_received(price_data.Timestamp);
        // writer->Write(msg);
        std::cout << "[write end]" << std::endl;
      }
    }

#endif
    return Status::OK;
  }

  Status GetContractInfo(ServerContext *context, const ContractInfoRequest *req,
                         ContractInfoResponse *writer) override {
    std::cout << "Received GetContractInfo request: " << std::endl;

    auto itr = req->contracts().cbegin();
    // ContractInfoResponse response;
    while (itr != req->contracts().cend()) {
      std::string val = itr->contract_id();
      auto pos = std::find_if(sharp_api_handle_->apiProdList_.begin(),
                              sharp_api_handle_->apiProdList_.end(),
                              [val](const SPApiProduct &p) {
                                return std::string(p.ProdCode) == val;
                              });
      if (pos == sharp_api_handle_->apiProdList_.end()) {
        std::cout << "cant find contract " << val << "[list size ]"
                  << sharp_api_handle_->apiProdList_.size() << std::endl;
      } else {
        ContractInfo *info = writer->add_contract_info_list();
        auto *p_contract = info->mutable_contract();
        p_contract->set_contract_id(val);
        // MarketCode to do
        p_contract->set_exchange(mdp::Exchange::EXCHANGE_UNDEFINED);
        std::cout << "[pos->ProdType] " << pos->ProdType << std::endl;
        printf("[pos->ProdType] %c \n", pos->ProdType);
        switch (pos->ProdType) {
          case 0:
            info->set_prod_type(mdp::COMMON);
            break;
          case 1:
            info->set_prod_type(mdp::FUT);
            break;
          case 2:
            info->set_prod_type(mdp::OPT);
            break;
          case 3:
            info->set_prod_type(mdp::COMBO);
            break;
        }
        std::string prodName = pos->ProdName;
        info->set_prod_name(prodName);

        std::string Underlying = pos->Underlying;
        info->set_underlying(Underlying);
        std::string InstCode = pos->InstCode;
        info->set_inst_code(InstCode);
        info->set_expiry_date(pos->ExpiryDate);
        std::string call_put;
        call_put.append(1, pos->CallPut);

        switch (pos->CallPut) {
          case 'C':
            info->set_call_put(CallPut::C);
            break;
          case 'P':
            info->set_call_put(CallPut::P);
            break;
          case '-':
            info->set_call_put(CallPut::NONE);
            break;
        }

        // info->set_call_put(call_put);
        printf("call put   %c \n", pos->CallPut);
        printf("call put   %d \n", pos->CallPut);
        std::cout << "call put" << call_put << std::endl;

        info->set_strike(pos->Strike);
        info->set_lot_size(pos->LotSize);

        // std::string ProdName1 = pos->ProdName1;
        // info->set_prod_name1(ProdName1);
        // std::string ProdName2 = pos->ProdName2;
        // info->set_prod_name2(ProdName2);
        std::string opt_type;
        opt_type.append(1, pos->OptStyle);

        cout << "[opt _type ]" << opt_type << std::endl;
        printf("opt_printf %d \n", pos->OptStyle);
        printf("opt_printf %c \n", pos->OptStyle);
        info->set_opt_type(opt_type);
        info->set_tick_size(pos->TickSize);

        std::string inst_code = pos->InstCode;
        auto inst_pos = std::find_if(sharp_api_handle_->apiInstList_.begin(),
                                     sharp_api_handle_->apiInstList_.end(),
                                     [inst_code](const SPApiInstrument &p) {
                                       return p.InstCode == inst_code;
                                     });
        if (inst_pos != sharp_api_handle_->apiInstList_.end()) {
          auto *margin = info->mutable_margin();
          margin->set_value(inst_pos->Margin);

          auto *contract_size = info->mutable_contract_size();
          contract_size->set_value(inst_pos->ContractSize);

          info->set_market_code(std::string(inst_pos->MarketCode));

          std::string InstCode = inst_pos->InstCode;
          std::string InstName1 = inst_pos->InstName1;
          std::string InstName2 = inst_pos->InstName2;

          info->set_inst_name(InstCode);
          // info->set_inst_name1(InstName1);
          //  info->set_inst_name2(InstName2);
          // std::string dec_price ;
          // dec_price.append(1, inst_pos->DecInPrice);
          int32_t dec_price = inst_pos->DecInPrice;
          info->set_dec_inprice(dec_price);
          printf("dec_price %d \n", inst_pos->DecInPrice);
          printf("dec_price %c \n", inst_pos->DecInPrice);

          std::cout << "[dec_price ]" << dec_price << std::endl;
          // to do
          if (std::string(inst_pos->Ccy) == "USD") {
            info->set_ccy(Currency::USD);

          } else if (std::string(inst_pos->Ccy) == "GBP") {
            info->set_ccy(Currency::GBP);

          } else if (std::string(inst_pos->Ccy) == "CNY") {
            info->set_ccy(Currency::CNY);

          } else if (std::string(inst_pos->Ccy) == "EUR") {
            info->set_ccy(Currency::EUR);

          } else if (std::string(inst_pos->Ccy) == "USDT") {
            info->set_ccy(Currency::USDT);

          } else if (std::string(inst_pos->Ccy) == "BTC") {
            info->set_ccy(Currency::BTC);

          } else if (std::string(inst_pos->Ccy) == "ETH") {
            info->set_ccy(Currency::ETH);

          } else if (std::string(inst_pos->Ccy) == "HKD") {
            info->set_ccy(Currency::HKD);

          } else if (std::string(inst_pos->Ccy) == "CNY") {
            info->set_ccy(Currency::CNY);
          } else if (std::string(inst_pos->Ccy) == "AUD") {
            info->set_ccy(Currency::AUD);
          } else if (std::string(inst_pos->Ccy) == "CHF") {
            info->set_ccy(Currency::CHF);
          } else if (std::string(inst_pos->Ccy) == "JPY") {
            info->set_ccy(Currency::JPY);
          } else if (std::string(inst_pos->Ccy) == "CAD") {
            info->set_ccy(Currency::CAD);
          } else if (std::string(inst_pos->Ccy) == "SGD") {
            info->set_ccy(Currency::SGD);
          }

          // info.set_ccy();
          // info.set_inst_type();
          std::cout << "inst_pos->InstType   " << inst_pos->InstType
                    << std::endl;
        } else {
          std::cout << "cant find  instrument " << val << std::endl;
        }

        switch (inst_pos->InstType) {
          case 0:
            info->set_inst_type(InstType::ACTIVE);
            break;
          case 1:
            info->set_inst_type(InstType::DELISTED);
            break;
          case 2:
            info->set_inst_type(InstType::OVERDUE);
            break;
        }

        // info->set_prod_type(pos->ProdType);
        // auto *margin = info->add_margin();
        // margin->set_value(pos->Margin);

        // writer->add_contract_info_list(info);
      }

      itr++;
    }

    //   timestamp_ex->set_seconds(  ((price->Timestamp / 10000000)));
    //   timestamp_ex->set_nanos( ((price->Timestamp % 10000000) * 100));

    struct timeval tv;
    gettimeofday(&tv, NULL);

    Timestamp *timestamp = writer->mutable_timestamp_received();
    timestamp->set_seconds(tv.tv_sec);
    timestamp->set_nanos(tv.tv_usec * 1000);

    Timestamp *timestamp_ex = writer->mutable_timestamp_exchange();
    timestamp_ex->set_seconds(tv.tv_sec);
    timestamp_ex->set_nanos(tv.tv_usec * 1000);
    std::cout << "write end" << std::endl;
    // writer->Write(response);
    return Status::OK;
  }

  Status GetTradingStatus(
      ServerContext *context,
      ServerReaderWriter<TradingStatusResponse, TradingStatusRequest> *stream)
      override {
    std::cout << "status peer: " << context->peer() << std::endl;

    std::string curr_peer = context->peer();

    if (client_id_.find(curr_peer) == client_id_.end()) {
      std::cout << "[new peer]" << std::endl;

      client_id_[curr_peer] = client_num_;
      client_num_++;
      sharp_api_handle_->status_handle_[curr_peer] = stream;  // set handle
      std::cout << "[curr peer num]" << client_id_.size() << std::endl;
    }

    int idx = client_id_[curr_peer];

    TradingStatusRequest var;
    while (stream->Read(&var)) {
      auto contract_itr = var.contracts().cbegin();

      sharp_api_handle_->lock();
      std::vector<std::string> sub_ticker;
      while (contract_itr != var.contracts().cend()) {
        std::string ticker = contract_itr->contract_id();
        sub_ticker.emplace_back(ticker);
        std::cout << " sub ticker " << ticker << std::endl;
        int rc;
        char prodcode[16], userId[16];
        strncpy(userId, sharp_api_handle_->getUserId().c_str(), 16);

        if (sharp_api_handle_->trade_status_contract_.find(ticker) !=
            sharp_api_handle_->trade_status_contract_.end()) {
          auto &client_set = sharp_api_handle_->trade_status_contract_[ticker];
          client_set.insert(curr_peer);

        } else {
          sharp_api_handle_->trade_status_contract_[ticker] =
              std::set<std::string>();
          sharp_api_handle_->trade_status_contract_[ticker].insert(curr_peer);
        }
        contract_itr++;
      }

      for (auto &ele : sub_ticker) {
        int rc = 0;
        rc = sharp_api_handle_->GetWrapperHandle()->SPAPI_SubscribePrice(
            userId, const_cast<char *>(ele.c_str()),
            1);  // idx, 0 :Uninit Price, 1:Subscribe Price, 2:set time to
                 // return to
        if (rc != 0) printf("\n SubscribeUninitPrice return:%d", rc);
      }

      sharp_api_handle_->unlock();
      while (true) {
        sleep(1);
        // do nothing
      }

      return Status::OK;
    }
  };

  ApiTester instance;

  void SharpPoint() {
    instance.RegisterReply();
    instance.HelpInfo();
    instance.Login();
    sleep(10);
    // instance.Test();
  }

  int main() {
    // std::thread sharp_api(SharpPoint);
    // sharp_api.detach();
    instance.RegisterReply();
    instance.HelpInfo();
    instance.Login();
    sleep(5);

    instance.LoadProductInfoListByMarket();
    sleep(5);
    instance.LoadInstrumentList();
    sleep(10);
    instance.GetProduct();
    // return 0;
    std::string server_address("0.0.0.0:5000");
    ServerNode service;
    service.sharp_api_handle_ = &instance;
    ServerBuilder builder;
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);
    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server: listening on " << server_address << std::endl;
    server->Wait();
    return 0;
  }
